package subterfuge;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.SwingUtilities;

import emergence.game.gfx.ColoredSprite;
import emergence.game.level.Level;
import emergence.game.level.tiles.Tile;

public class ViewerWindow extends Canvas {

    private static final long serialVersionUID = 1L;

    private ViewerListener viewerListener;
    private SubterfugeEditor editor;

    private Level level;

    private BufferedImage image;
    private int[] pixels;

    private int imageWidth;
    private int imageHeight;

    private double xOffset; // Distance the left edge of the screen is offset from the left edge of the level, in Tiles
    private double yOffset; // Distance the top edge of the screen is offset from the top edge of the level, in Tiles
    private double scale; // The amount of pixels each Tile is rendered on the side.

    private int[] colors = new int[216];

    /**
     * A class representing the viewer window, or screen, of the editor.
     *
     * @param editor the SubterfugeEditor for which this is serving as a window.
     */
    public ViewerWindow(SubterfugeEditor editor) {
        this.editor = editor;
        this.viewerListener = new ViewerListener();
        this.addMouseListener(viewerListener);
        this.addMouseWheelListener(viewerListener);
        this.addMouseMotionListener(viewerListener);
        init();
    }

    /**
     * Initializes the various fields for the viewer.
     */
    private void init() {
        xOffset = 0.0;
        yOffset = 0.0;
        scale = 2.0;

        int index = 0;
        for (int r = 0; r < 6; r++) {
            for (int g = 0; g < 6; g++) {
                for (int b = 0; b < 6; b++) {
                    int rr = (r * 255 / 5);
                    int gg = (g * 255 / 5);
                    int bb = (b * 255 / 5);

                    colors[index] = rr << 16 | gg << 8 | bb;
                    index++;
                }
            }
        }
    }

    // Needs to be reworked for more performance
    /**
     * Renders the level to the screen
     */
    public void render() {

        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }

        double curXOff = xOffset;
        double curYOff = yOffset;
        double curScale = this.scale;

        imageWidth = getWidth();
        imageHeight = getHeight();
        image = new BufferedImage(imageWidth, imageHeight,
                BufferedImage.TYPE_INT_RGB);
        pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

        if (level == null) {
            for (int i = 0; i < pixels.length; i++) {
                pixels[i] = 0xFFFFFF;
            }
        }
        else if (!level.isRenderable()) {
            return;
        }
        
        else {
            double pixelsPerTile = (double) level.TILE_SIZE * curScale;
            for (int i = 0; i < imageWidth; i++) {
                for (int j = 0; j < imageHeight; j++) {
                    double xPos = i + curXOff * level.TILE_SIZE * curScale;
                    double yPos = j + curYOff * level.TILE_SIZE * curScale;
                    double xTile = xPos / (level.TILE_SIZE * curScale);
                    double yTile = yPos / (level.TILE_SIZE * curScale);
                    if (xTile < 0 || yTile < 0 || xTile > level.width() || yTile > level.height()) {
                        pixels[i + (j * imageWidth)] = 0xFFFFFF;
                    }
                    else {
                        int col = getPixel(level, xPos / pixelsPerTile, yPos / pixelsPerTile);

                        if (col < 255) {
                            pixels[i + (j * imageWidth)] = colors[col];
                        }
                    }
                }
            }
        }
//        else {
//            double pixelsPerTile = (double) level.TILE_SIZE * curScale;
//            int leftTile = (int) Math.floor(curXOff);
//            int topTile = (int) Math.floor(curYOff);
//            int tileHeight = (int) Math.ceil(imageWidth / pixelsPerTile) + 1;
//            int tileWidth = (int) Math.ceil(imageHeight / pixelsPerTile) + 1;
//            
//            for (int i = 0; i < tileWidth; i++) {
//                for (int j = 0; j < tileHeight; j++) {
//                    int xPos = leftTile + i;
//                    int yPos = topTile + j;
//                    this.renderTile(curXOff + (xPos * level.TILE_SIZE), curYOff + (yPos * level.TILE_SIZE), level.getTile(xPos, yPos).sprite());
//                }
//            }
//        }

        Graphics graphics = bs.getDrawGraphics();
        graphics.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        graphics.dispose();
        bs.show();
    }
    
    private void renderTile(double x, double y, ColoredSprite sprite) {
        int leftX = (int) Math.round(Math.floor(x));
        int topY = (int) Math.round(Math.floor(y));
        
        for (int i = 0; i < sprite.width(); i++) {
            for (int j = 0; j < sprite.height(); j++) {
                int xPos = i + leftX;
                int yPos = j + topY;
                if (xPos < 0 || xPos > imageWidth || yPos < 0 || yPos > imageHeight) {
                    continue;
                }
                int color = sprite.pixels()[i][j];
                if (color < 255) {
                    pixels[xPos + (yPos * imageWidth)] = colors[color];
                }
            }
        }
    }

    /**
     * Loads the provided level into the viewer.
     * @param level the level to be loaded into and rendered by the viewer
     */
    public void loadLevel(Level level) {
        this.level = level;
        scale = 2.0;
        xOffset = 0.0;
        yOffset = 0.0;
    }

    /**
     * Gets the integer representing the RGB for the pixel at the specified
     * location
     *
     * @param x the X position of the desired pixel, in Tiles (or fractions of
     *          a Tile)
     * @param y the Y position of the desired pixel, in Tiles (or fractions of
     *          a Tile)
     *
     * @return Returns the integer representing the RGB for the pixel at the
     *         specified location.
     */
    private int getPixel(Level level, double x, double y) {
        int tileX = (int) Math.round(Math.floor(x));
        int tileY = (int) Math.round(Math.floor(y));

        int xOff = (int) Math.round(Math.floor((x % 1.0) * level.TILE_SIZE));
        int yOff = (int) Math.round(Math.floor((y % 1.0) * level.TILE_SIZE));

        Tile tile = level.getTile(tileX, tileY);
        ColoredSprite sprite = tile.sprite();
        return sprite.pixels()[xOff][yOff];
    }

    /**
     * Returns the x location (in Tiles) of the provided pixel location.
     * @param x the query location, in pixels
     * @return Returns the Tile location, in Tiles, of the provided pixel location.
     */
    private double getXOfPixel(double x) {
        return xOffset + x / (level.TILE_SIZE * scale);
    }

    /**
     * Returns the y location (in Tiles) of the provided pixel location.
     * @param y the query location, in pixels
     * @return Returns the Tile location, in Tiles, of the provided pixel location.
     */
    private double getYOfPixel(double y) {
        return yOffset + y / (level.TILE_SIZE * scale);
    }

    /**
     * A listener that responds to mouse input over the viewer.
     */
    private class ViewerListener implements MouseListener, MouseMotionListener, MouseWheelListener {

        private boolean leftButtonPressed = false;
        private boolean rightButtonPressed = false;
        private boolean middleButtonPressed = false;

        private int panStartX;
        private int panStartY;
        private double panStartXOff;
        private double panStartYOff;

        @Override
        public void mouseDragged(MouseEvent e) {
            if (leftButtonPressed) {
                if (level == null) {
                    return;
                }

                int x = (int) getXOfPixel(e.getX());
                int y = (int) getYOfPixel(e.getY());

                level.setTile(x, y, editor.currentTile());
            }
            if (rightButtonPressed) {

            }
            if (middleButtonPressed) {
                if (level == null) {
                    return;
                }

                double xChange = (panStartX - e.getX()) / (level.TILE_SIZE * scale);
                double yChange = (panStartY - e.getY()) / (level.TILE_SIZE * scale);

                xOffset = panStartXOff + xChange;
                yOffset = panStartYOff + yChange;
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {

        }

        @Override
        public void mouseClicked(MouseEvent e) {

        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                leftButtonPressed = true;

                if (level == null) {
                    return;
                }

                int x = (int) getXOfPixel(e.getX());
                int y = (int) getYOfPixel(e.getY());

                level.setTile(x, y, editor.currentTile());
            }
            else if (SwingUtilities.isRightMouseButton(e)) {
                rightButtonPressed = true;
                // Open Context Menu
            }
            else if (SwingUtilities.isMiddleMouseButton(e)) {
                if (level == null) {
                    return;
                }

                middleButtonPressed = true;

                panStartX = e.getX();
                panStartY = e.getY();

                panStartXOff = xOffset;
                panStartYOff = yOffset;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                leftButtonPressed = false;
            }
            else if (SwingUtilities.isRightMouseButton(e)) {
                rightButtonPressed = false;
            }
            else if (SwingUtilities.isMiddleMouseButton(e)) {
                middleButtonPressed = false;
            }
        }

        @Override
        public void mouseEntered(MouseEvent e) {

        }

        @Override
        public void mouseExited(MouseEvent e) {

        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            // Change to zoom in on center instead of upper left corner
            // - Change xOffset and yOffset to compensate

            if (middleButtonPressed || level == null) {
                return;
            }

            int notches = e.getWheelRotation();

            double centerX = getXOfPixel(getWidth() / 2.0);
            double centerY = getYOfPixel(getHeight() / 2.0);

            if (notches > 0) { // Zoom in
                scale = scale * 0.9;
            }
            else if (notches < 0) { // Zoom out
                scale = scale / 0.9;
            }

            xOffset = centerX - getWidth() / (2.0 * level.TILE_SIZE * scale);
            yOffset = centerY - getHeight() / (2.0 * level.TILE_SIZE * scale);
        }

    }

}
